package opencv.service.algorithms;

import static com.googlecode.javacv.jna.cv.CV_BGR2GRAY;
import static com.googlecode.javacv.jna.cv.CV_HAAR_FIND_BIGGEST_OBJECT;
import static com.googlecode.javacv.jna.cv.CV_INTER_LINEAR;
import static com.googlecode.javacv.jna.cv.cvCvtColor;
import static com.googlecode.javacv.jna.cv.cvEqualizeHist;
import static com.googlecode.javacv.jna.cv.cvHaarDetectObjects;
import static com.googlecode.javacv.jna.cv.cvResize;
import static com.googlecode.javacv.jna.cxcore.CV_RGB;
import static com.googlecode.javacv.jna.cxcore.IPL_DEPTH_8U;
import static com.googlecode.javacv.jna.cxcore.cvCopy;
import static com.googlecode.javacv.jna.cxcore.cvCreateImage;
import static com.googlecode.javacv.jna.cxcore.cvCreateMemStorage;
import static com.googlecode.javacv.jna.cxcore.cvGetSeqElem;
import static com.googlecode.javacv.jna.cxcore.cvGetSize;
import static com.googlecode.javacv.jna.cxcore.cvLoad;
import static com.googlecode.javacv.jna.cxcore.cvPoint;
import static com.googlecode.javacv.jna.cxcore.cvRectangle;
import static com.googlecode.javacv.jna.cxcore.cvReleaseImage;
import static com.googlecode.javacv.jna.cxcore.cvSize;

import java.awt.Image;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.List;

import opencv.service.Algorithm;

import com.googlecode.javacv.jna.cv.CvHaarClassifierCascade;
import com.googlecode.javacv.jna.cxcore.CvMemStorage;
import com.googlecode.javacv.jna.cxcore.CvPoint;
import com.googlecode.javacv.jna.cxcore.CvRect;
import com.googlecode.javacv.jna.cxcore.CvSeq;
import com.googlecode.javacv.jna.cxcore.IplImage;

/**
 * @author Aakash Polra
 */
public class FaceDetection implements Algorithm {

	public static final String DEFAULT_OPENCV2_FACE_CASCADE = "C:/OpenCV2.0/data/haarcascades/haarcascade_frontalface_alt.xml";
	public static String DEFAULT_OPENCV2_NOSE_CASCADE = "C:/OpenCV2.0/data/haarcascades/haarcascade_mcs_nose.xml";
	public static String DEFAULT_OPENCV2_EYES_CASCADE = "C:/OpenCV2.0/data/haarcascades/haarcascade_mcs_eyepair_small.xml";
	
	public static final float HAAR_IMAGE_SCALE = 0.75f;

	
	private CvHaarClassifierCascade faceCascade = null;
	private CvHaarClassifierCascade faceFeatureCascade = null;

	private IplImage transImage = null;
	
	private List<Rectangle> lastFoundFaces;
	
	private String faceCascadePath;
	private String faceFeatureCascadePath;
	
	private boolean detectOnlyOneFace;

	CvMemStorage storage;
	CvMemStorage eyesStorage;


	public FaceDetection(String faceCascadePath, String eyesCascadePath) {
		this.faceCascadePath = faceCascadePath;
		this.faceFeatureCascadePath = eyesCascadePath;
		this.lastFoundFaces = new ArrayList<Rectangle>();
		
		storage = cvCreateMemStorage(0); // dynamic size
		eyesStorage = cvCreateMemStorage(0);
	}
	
	
	public static boolean isInside(CvRect container, CvRect insideRect) {
		if (container != null && insideRect != null) {
			if (insideRect.x > container.x && insideRect.y > container.y && insideRect.x + insideRect.width < container.x + container.width
					&& insideRect.y + insideRect.height < container.y + container.height) {
				return true;
			}
		}
		return false;
	}

	private boolean isFoundInside(CvSeq objects, CvRect rect) {
		boolean found = false;

		for (int j = 0; j < objects.total; j++) {
			CvRect object_rect = new CvRect(cvGetSeqElem(objects, j));

			if (!isInside(rect, object_rect)) {
				continue;
			} else {
				found = true;
				//CvPoint rectTopLeft = cvPoint((int) (object_rect.x / HAAR_IMAGE_SCALE), (int) (object_rect.y / HAAR_IMAGE_SCALE));
				//CvPoint rectBotRight = cvPoint((int) ((object_rect.x + object_rect.width) / HAAR_IMAGE_SCALE), (int) ((object_rect.y + object_rect.height) / HAAR_IMAGE_SCALE));
				//cvRectangle(transImage, rectTopLeft.byValue(), rectBotRight.byValue(), CV_RGB(0, 255, 0), 1, 8, 0);
				break; // done for this circle.
			}
		}
		return found;
	}
	
	

	@Override
	public void run(IplImage img1, IplImage img2) {
		
		lastFoundFaces.clear();
		
		IplImage greyImage = cvCreateImage(cvGetSize(img2), IPL_DEPTH_8U, 1);
		cvCvtColor(img2, greyImage, CV_BGR2GRAY);

		if (faceCascade == null) {
			faceCascade = new CvHaarClassifierCascade(cvLoad(faceCascadePath, null, null, null));
		}
		if (faceFeatureCascade == null) {
			faceFeatureCascade = new CvHaarClassifierCascade(cvLoad(faceFeatureCascadePath, null, null, null));
		}

		if (transImage != null) {
			cvReleaseImage(transImage.pointerByReference());
		}

		transImage = cvCreateImage(cvGetSize(img2), IPL_DEPTH_8U, img2.nChannels);
		cvCopy(img2, transImage, null);

		// scale the greyscale image to improve performance
		IplImage scaledGreyImage = cvCreateImage(cvSize((int) (greyImage.width * HAAR_IMAGE_SCALE), (int) (greyImage.height * HAAR_IMAGE_SCALE)), 8, 1);
		cvResize(greyImage, scaledGreyImage, CV_INTER_LINEAR);
		cvEqualizeHist(scaledGreyImage, scaledGreyImage);
		// detect the faces in the greyscale image

//		long start = System.currentTimeMillis();
		
		CvSeq faceObjects = cvHaarDetectObjects(scaledGreyImage, faceCascade, storage, 1.1, 5, 0, cvSize(20, 30));
		
//		long face = System.currentTimeMillis() - start;

		int flags = detectOnlyOneFace ? CV_HAAR_FIND_BIGGEST_OBJECT : 0;
		
		CvSeq eyesObjects = cvHaarDetectObjects(scaledGreyImage, faceFeatureCascade, eyesStorage, 1.5, 2, flags, cvSize(15, 5));
		cvReleaseImage(scaledGreyImage.pointerByReference());

//		long feature = System.currentTimeMillis() - start - face;
		
		boolean faceFound = false;
		for (int i = 0; i < faceObjects.total; i++) {
			faceFound = false;
			CvRect rect = new CvRect(cvGetSeqElem(faceObjects, i));

			// check if any eyes exist in that face
			faceFound = isFoundInside(eyesObjects, rect);

			if (!faceFound) {
				continue; // don't bother if no eyes found in face
			}
			
			cvReleaseImage(greyImage.pointerByReference());
//			cvReleaseMemStorage(storage.pointerByReference());
//			cvReleaseMemStorage(eyesStorage.pointerByReference());

			lastFoundFaces.add(new Rectangle((int) (rect.x / HAAR_IMAGE_SCALE), (int) (rect.y / HAAR_IMAGE_SCALE), (int) (rect.width / HAAR_IMAGE_SCALE),
					(int) (rect.height / HAAR_IMAGE_SCALE)));
			
			
			CvPoint rectTopLeft = cvPoint((int) (rect.x / HAAR_IMAGE_SCALE), (int) (rect.y / HAAR_IMAGE_SCALE));
			CvPoint rectBotRight = cvPoint((int) ((rect.x + rect.width) / HAAR_IMAGE_SCALE), (int) ((rect.y + rect.height) / HAAR_IMAGE_SCALE));
			cvRectangle(transImage, rectTopLeft.byValue(), rectBotRight.byValue(), CV_RGB(0, 255, 0), 3, 8, 0);
			
//			return transImage.getBufferedImage().getSubimage((int) (rect.x / HAAR_IMAGE_SCALE), (int) (rect.y / HAAR_IMAGE_SCALE), (int) (rect.width / HAAR_IMAGE_SCALE),
//					(int) (rect.height / HAAR_IMAGE_SCALE));
		
		}
		
//		long other = System.currentTimeMillis() - start - feature;
		
//		System.out.println("face[" + faceObjects.total + "]: " + face + "\teyes[" + eyesObjects.total + "]: " + feature + "\tother: " + other);
		
		onDebugImagePrepared(transImage.getBufferedImage());
	}

	@Override
	public void onDebugImagePrepared(Image image) {
		
	}
	
	public String getFaceCascadeName() {
		return faceCascadePath;
	}
	
	public void setFaceCascadeName(String faceCascadeName) {
		this.faceCascadePath = faceCascadeName;
	}
	
	public void setEyesCascadeName(String eyesCascadeName) {
		this.faceFeatureCascadePath = eyesCascadeName;
	}
	
	public String getEyesCascadeName() {
		return faceFeatureCascadePath;
	}
	
	public List<Rectangle> getLastFoundFaces() {
		return lastFoundFaces;
	}
}
